/**
 * 碰撞控制器 - WASD移动、拖动鼠标旋转镜头，与 TourControls 很像，但添加了像物理引擎一样的碰撞效果
 * 人体模型：胶囊体，半径0.3m，人高1.6m，视线高1.5m，胶囊中部圆柱高1.0m
 *
 * 功能
 *  - 辅助器
 *  - 复杂模型剔除机制：为它添加一个碰撞盒，并为盒子设置一个特殊的名字
 *
 * 用法
 * 1. 导入
 * import CollideControls from '@/scripts/three/CollideControls'
 * 2. 声明
 * const controls = new CollideControls(camera, renderer.domElement)
 * 3. 烘焙八叉树（在场景加载完成之后）
 * controls.bake(object)
 * 4. 更新（在渲染循环内）
 * controls.update()
 *
 * TODO
 *  [ ] 设置：初始位置设置
 *  [ ] 点位拾取后，世界会上下颠倒，应该是与改变了相机旋转的 order 导致的
 *  [ ] 与点位动画有冲突，现在没有 target 了，就无法控制朝向了
 *  [ ] 与点位动画有冲突，因为物理引擎认为人不能飞到天上去
 */

import * as THREE from 'three';
import { Octree } from 'three/examples/jsm/math/Octree.js';
import { OctreeHelper } from 'three/examples/jsm/helpers/OctreeHelper.js';
import { Capsule } from 'three/examples/jsm/math/Capsule.js';

// import type { Number5 } from './types'
// import CameraNavigator from './CameraNavigator'

type CollideControlsOptions = {
  enableJump?: boolean; // 启用跳跃功能，默认 true
};

// 常量
const GRAVITY = 20; // 重力参数
const JUMP_VELOCITY = 10; // 跳跃速度/高度，参考值：15=3m, 13=2m, 10=1m，8=0.5
const distEyeToTop = 0.1; // 眼到头顶的距离
const playerRadius = 0.3; // 玩家胶囊体的半径
const playerHeight = 1.6; // 玩家高度（取女人的平均高度，是为了综合男人和儿童）

// 变量
const cameraDirection = new THREE.Vector3();
const up = new THREE.Vector3(0, 1, 0);
const cameraRight = new THREE.Vector3();
const colliderEndToEye = new THREE.Vector3(0, playerRadius - distEyeToTop);
const eyePos = new THREE.Vector3();

export default class CollideControls {
  // 主属性
  enabled = true; // 是否启用
  enableJump = true; // 是否启用跳跃功能
  // 关联对象
  camera: THREE.PerspectiveCamera;
  // cameraNavigator: CameraNavigator
  dom: HTMLElement;
  constructor(
    camera: THREE.PerspectiveCamera,
    dom: HTMLElement,
    options: CollideControlsOptions = {}
  ) {
    this.camera = camera;
    // this.cameraNavigator = new CameraNavigator(this.camera)
    this.dom = dom;
    this.camera.rotation.order = 'YXZ';
    // 选项
    if (options.enableJump != null) this.enableJump = options.enableJump;

    // 鼠标
    this.onMouseDown = this._onMouseDown.bind(this);
    this.onMouseUp = this._onMouseUp.bind(this);
    this.onMouseMove = this._onMouseMove.bind(this);
    this.dom.addEventListener('mousedown', this.onMouseDown);
    this.dom.addEventListener('mouseup', this.onMouseUp);
    this.dom.addEventListener('mousemove', this.onMouseMove);
    // 键盘
    this.onKeyDown = this._onKeyDown.bind(this);
    this.onKeyUp = this._onKeyUp.bind(this);
    document.body.addEventListener('keydown', this.onKeyDown);
    document.body.addEventListener('keyup', this.onKeyUp);
  }

  // 物理
  clock = new THREE.Clock();
  worldOctree = new Octree();
  playerCollider = new Capsule(
    new THREE.Vector3(0, playerRadius, 0),
    new THREE.Vector3(0, playerHeight - playerRadius, 0),
    playerRadius
  );
  playerVelocity = new THREE.Vector3();
  playerDirection = new THREE.Vector3();
  playerOnFloor = false;
  helper = new OctreeHelper(this.worldOctree); // 世界八叉树的辅助器，默认隐藏，需要请手动添加到场景并设置visible

  // 飞行到（兼容特写功能）
  flying = true;
  // flyTo(cameraPos: Number5) {
  //   this.flying = true
  //   this.cameraNavigator.flyCameraTo(...cameraPos, 2)
  //   setTimeout(() => {
  //     this.teleportPlayerTo(
  //       this.camera.position.x,
  //       this.camera.position.y,
  //       this.camera.position.z,
  //       cameraPos[3],
  //       cameraPos[4],
  //     )
  //     this.flying = false
  //   }, 1000 * 2)
  // }

  // 烘焙八叉树
  baked = false;
  world?: THREE.Object3D;
  bake(world: THREE.Object3D) {
    this.baked = false;
    console.log(`[CollideControls] 开始烘焙...`);
    const timeStart = Date.now();
    this._removeColliders(world);
    this.worldOctree.fromGraphNode(world);
    this._restoreColliders(world);
    this.helper.update();
    const timeEnd = Date.now();
    const elapsedTime = (timeEnd - timeStart) / 1000;
    console.log(`[CollideControls] 烘焙完成，共耗时 ${elapsedTime} 秒`);
    this.baked = true;
    this.world = world;
  }

  // 烘焙剔除机制：临时移除碰撞体（name: Collider_）的子节点
  colliderChildren: { [name: string]: THREE.Object3D[] } = {};
  _removeColliders(object: THREE.Object3D) {
    object.traverse((_node) => {
      if (_node.name.startsWith('Collider_')) {
        this.colliderChildren[_node.name] = [..._node.children];
        _node.clear();
        // 顺便把半透改为全透
        const node = _node as THREE.Mesh;
        const material = node.material as THREE.MeshStandardMaterial;
        if (material) material.opacity = 0;
      }
    });
  }
  _restoreColliders(object: THREE.Object3D) {
    object.traverse((node) => {
      if (node.name.startsWith('Collider_')) {
        const children = this.colliderChildren[node.name];
        children.forEach((child) => {
          node.add(child);
        });
      }
    });
  }

  // 调试：显示/隐藏空气墙
  showAirWalls() {
    this.setAirWalls(0.25, 0x0080ff);
  }
  hideAirWalls() {
    this.setAirWalls(0, 0xffffff);
  }
  setAirWalls(opacity: number, color: number) {
    if (this.world == null) return;
    this.world.traverse((_node) => {
      if (_node.name.startsWith('Collider_')) {
        const node = _node as THREE.Mesh;
        const material = node.material as THREE.MeshStandardMaterial;
        if (material) {
          material.opacity = opacity;
          material.color.set(color);
        }
      }
    });
  }

  // 玩家与世界碰撞
  playerCollisions() {
    const result = this.worldOctree.capsuleIntersect(this.playerCollider);
    this.playerOnFloor = false;
    if (result) {
      this.playerOnFloor = result.normal.y > 0;
      if (!this.playerOnFloor) {
        this.playerVelocity.addScaledVector(
          result.normal,
          -result.normal.dot(this.playerVelocity)
        );
      }
      this.playerCollider.translate(result.normal.multiplyScalar(result.depth));
    }
  }

  // 更新控制
  updateControls(deltaTime: number) {
    // 更新前和右向量
    cameraDirection.set(0, 0, -1);
    cameraDirection.applyQuaternion(this.camera.quaternion).normalize();
    cameraRight.crossVectors(cameraDirection, up).normalize();

    const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8);
    // 前后左右
    if (this.pressedKeyMap['w']) {
      this.playerVelocity.add(cameraDirection.multiplyScalar(speedDelta));
    }
    if (this.pressedKeyMap['s']) {
      this.playerVelocity.add(cameraDirection.multiplyScalar(-speedDelta));
    }
    if (this.pressedKeyMap['a']) {
      this.playerVelocity.add(cameraRight.multiplyScalar(-speedDelta));
    }
    if (this.pressedKeyMap['d']) {
      this.playerVelocity.add(cameraRight.multiplyScalar(speedDelta));
    }
    // 跳跃
    if (this.pressedKeyMap[' '] && this.enableJump && this.playerOnFloor) {
      this.playerVelocity.y = JUMP_VELOCITY;
    }
  }

  // 更新玩家
  updatePlayer(deltaTime: number) {
    let damping = Math.exp(-4 * deltaTime) - 1;
    if (!this.playerOnFloor) {
      this.playerVelocity.y -= GRAVITY * deltaTime;
      damping *= 0.1;
    }
    this.playerVelocity.addScaledVector(this.playerVelocity, damping);

    const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
    this.playerCollider.translate(deltaPosition);
    this.playerCollisions();
    eyePos.addVectors(this.playerCollider.end, colliderEndToEye);
    this.camera.position.copy(eyePos);
  }

  // 如果玩家脱离世界，传送到原点
  teleportPlayerIfOob() {
    if (this.camera.position.y <= -25)
      this.teleportPlayerTo(0, playerHeight - distEyeToTop, 0, 0, 0);
  }
  teleportPlayerTo(eyeX: number, eyeY: number, eyeZ: number, rx = 0, ry = 0) {
    const footY = eyeY - playerHeight - distEyeToTop;
    this.playerCollider.start.set(eyeX, footY + playerRadius, eyeZ);
    this.playerCollider.end.set(
      eyeX,
      footY + playerHeight - playerRadius,
      eyeZ
    );
    this.playerCollider.radius = playerRadius;
    eyePos.addVectors(this.playerCollider.end, colliderEndToEye);
    this.camera.position.copy(eyePos);
    this.camera.rotation.set(rx, ry, 0);
  }

  // 鼠标
  isMouseLeft = false;
  onMouseDown: (e: MouseEvent) => void;
  onMouseUp: (e: MouseEvent) => void;
  onMouseMove: (e: MouseEvent) => void;
  _onMouseDown(e: MouseEvent) {
    if (!this.enabled) return;

    // console.log('[TourControls] onMouseDown')
    if (e.buttons == 1) this.isMouseLeft = true;
  }
  _onMouseUp(e: MouseEvent) {
    if (!this.enabled) return;

    // console.log('[TourControls] onMouseUp')
    this.isMouseLeft = false;
  }
  _onMouseMove(e: MouseEvent) {
    if (!this.enabled) return;

    // console.log('[TourControls] onMouseMove', this.isMouseLeft)
    if (this.isMouseLeft) {
      // console.log('拖拽相机', e.movementX, e.movementY)
      this.camera.rotation.y += e.movementX / 600;
      this.camera.rotation.x += e.movementY / 600;
    }
  }

  // 键盘
  pressedKeyMap: { [key: string]: boolean } = {};
  onKeyDown: (e: KeyboardEvent) => void;
  onKeyUp: (e: KeyboardEvent) => void;
  _onKeyDown(e: KeyboardEvent) {
    if (!this.enabled) return;

    // console.log('[TourControls] onKeyDown', e.key)
    this.pressedKeyMap[e.key] = true;
  }
  _onKeyUp(e: KeyboardEvent) {
    if (!this.enabled) return;

    // console.log('[TourControls] onKeyUp', e.key)
    this.pressedKeyMap[e.key] = false;
  }

  update() {
    if (this.baked == false) return;
    const deltaTime = this.clock.getDelta();
    this.updateControls(deltaTime);
    this.updatePlayer(deltaTime);
    this.teleportPlayerIfOob();
  }
}
